package com.jmz.apt.api.filter;

import com.jmz.apt.api.Helper.ResponseHelper;
import com.jmz.apt.api.Utils.JsonSerializerUtil;
import com.jmz.apt.api.Utils.MD5Util;
import com.jmz.apt.api.Utils.TokenUtil;
import com.jmz.apt.api.common.AptConstant;
import com.jmz.apt.api.common.SecurityContextHolder;
import com.jmz.apt.api.configure.ExclusionUrl;
import com.jmz.apt.api.dto.impl.AdminTokenDto;
import com.jmz.apt.api.enums.ResponseCode;
import com.jmz.apt.api.service.LoginService;
import com.jmz.apt.api.service.auth.Token;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;

import io.jsonwebtoken.Claims;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * Created by fanglexue on 2019/10/25.
 */
@Slf4j
public class LoginFilter implements Filter {

    private StringRedisTemplate stringRedisTemplate;

    private RedisTemplate<String, Serializable> redisTemplate;

    private Token token;

    private ResponseHelper responseHelper;

    private LoginService loginService;

    public LoginFilter(StringRedisTemplate stringRedisTemplate, RedisTemplate<String, Serializable> redisTemplate,
                       Token token, ResponseHelper responseHelper, LoginService logser) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.redisTemplate = redisTemplate;
        this.token = token;
        this.responseHelper = responseHelper;
        this.loginService = logser;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        HttpServletResponse servletResponse = (HttpServletResponse) response;

        String url = servletRequest.getRequestURI().substring(servletRequest.getContextPath().length());

        if (url.startsWith("/") && url.length() > 1) {
            url = url.substring(1);
        }
        if (ExclusionUrl.isInclude(url)) {
            chain.doFilter(servletRequest, servletResponse);
            return;
        }

        String token = servletRequest.getHeader("token");
        if (StringUtils.isBlank(token)) {
            responseHelper.setResponseBCode(ResponseCode.NO_LOGIN.getCode(),ResponseCode.NO_LOGIN.getDesc());
            responseHelper.outPut(servletResponse);
            return;
        }
        //检查登录是否失效
        String loginFlag = stringRedisTemplate.opsForValue().get(AptConstant.Redis.TOKEN_INVALID + MD5Util.encode(token));
        if (StringUtils.isEmpty(loginFlag) && null == loginFlag) {
            redisTemplate.opsForHash().delete(AptConstant.Redis.LOGIN_AGENTS,AptConstant.Redis.AGENT_TOKEN + MD5Util.encode(token));
            responseHelper.setResponseBCode(ResponseCode.LOGIN_LOSE_EFFICACY.getCode(),ResponseCode.LOGIN_LOSE_EFFICACY.getDesc());
            responseHelper.outPut(servletResponse);
            return;
        }
        try {
            //刷新登录时间
            stringRedisTemplate.expire(AptConstant.Redis.TOKEN_INVALID + MD5Util.encode(token),30 * 60,TimeUnit.SECONDS);
            Claims claims = TokenUtil.parseJWT(token);
            String subject = claims.getSubject();
            AdminTokenDto adminTokenDto = JsonSerializerUtil.string2Obj(subject, AdminTokenDto.class);
            Boolean hasAdmin = redisTemplate.opsForHash().hasKey(AptConstant.Redis.LOGIN_AGENTS,
                    AptConstant.Redis.AGENT_TOKEN + MD5Util.encode(token));
            if (!hasAdmin.booleanValue()) {
                List<AdminTokenDto> dbAdmins = this.token.findAdminUserAsRoleByAdminId(adminTokenDto.getAdminId());
                AdminTokenDto dbAdmin = dbAdmins.get(0);
                if (dbAdmin == null) {
                    responseHelper.setResponseBCode(ResponseCode.LOGIN_EXCEPTION.getCode(),ResponseCode.LOGIN_EXCEPTION.getDesc());
                    responseHelper.outPut(servletResponse);
                    return;
                }
                redisTemplate.opsForHash().put(AptConstant.Redis.LOGIN_AGENTS,
                        AptConstant.Redis.AGENT_TOKEN + MD5Util.encode(token), dbAdmin);
            }

            AdminTokenDto agent =  (AdminTokenDto) redisTemplate.opsForHash().get(AptConstant.Redis.LOGIN_AGENTS,AptConstant.Redis.AGENT_TOKEN + MD5Util.encode(token));
            SecurityContextHolder.addAgent(agent);
            chain.doFilter(servletRequest, servletResponse);
            return;
        } catch (ExpiredJwtException e) {
            log.error("Token已失效");
            AdminTokenDto adminTokenDto = (AdminTokenDto) redisTemplate.opsForHash().get(AptConstant.Redis.LOGIN_AGENTS,
                    AptConstant.Redis.AGENT_TOKEN + MD5Util.encode(token));
            boolean isValid = stringRedisTemplate.hasKey(AptConstant.Redis.TOKEN_REFRESH + MD5Util.encode(token)).booleanValue();
            if (isValid && null != adminTokenDto) {
                //refresh token  仍然有效
                Boolean hasKey = stringRedisTemplate.hasKey(AptConstant.Redis.TOKEN_BLACKLIST + MD5Util.encode(token));
                if (hasKey.booleanValue()) {
                    //解决并发环境下问题
                    return;
                }
                //BeanUtils.copyProperties(agent,adminTokenDto);
                String newToken = TokenUtil.conferToken(adminTokenDto, AptConstant.TOKEN_EXPIRE);
                HttpServletResponse httpServletResponse = (HttpServletResponse) response;
                HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                httpServletResponse.setHeader("Cache-Control", "no-store");
                httpServletResponse.setHeader("token",  newToken);

                loginService.setRefreshToken(newToken, token);
                //旧Token加入黑名单,解决并发下的问题
                loginService.setTokenBackList(newToken, token);
                //继续当前请求,状态仍然需要维护
                List<AdminTokenDto> dbAdmins = this.token.findAdminUserAsRoleByAdminId(adminTokenDto.getAdminId());
                AdminTokenDto dbAdmin = dbAdmins.get(0);
                loginService.setTokenRedisMap(newToken, dbAdmin);
                SecurityContextHolder.addAgent(dbAdmin);
                responseHelper.setResponseBCode(ResponseCode.REFRESH_TOKEN.getCode(),newToken);
                responseHelper.outPut(servletResponse);
                return;
            }
            responseHelper.setResponseBCode(ResponseCode.LOGIN_LOSE_EFFICACY.getCode(),ResponseCode.LOGIN_LOSE_EFFICACY.getDesc());
            responseHelper.outPut(servletResponse);
            return;
        } catch (MalformedJwtException e) {
            log.error("Token校验错误");
            responseHelper.setResponseBCode(ResponseCode.AUTHENTICATION_FAILURE.getCode(),ResponseCode.AUTHENTICATION_FAILURE.getDesc());
            responseHelper.outPut(servletResponse);
            return;
        } catch (SignatureException e) {
            log.error("签名错误");
            responseHelper.setResponseBCode(ResponseCode.AUTHENTICATION_FAILURE.getCode(),ResponseCode.AUTHENTICATION_FAILURE.getDesc());
            responseHelper.outPut(servletResponse);
            return;
        }


    }







    @Override
    public void destroy() {

    }
}
